iT邦幫忙

2024 iThome 鐵人賽

DAY 17
1
Software Development

可以Go一輩子嗎?系列 第 17

Day17. select語句及其在非同步中的應用

  • 分享至 

  • xImage
  •  

Day17. select語句及其在非同步中的應用

介紹

在Go語言中,select是一個強大的控制結構(類似於switch),可以用來處理多個channel的發送和接收操作。我們可以透過select同時監聽多個channel,並且在case符合時執行對應的操作。或是持續執行特定行為(前提是有default分支)

在select 開始前,會先依照case順序執行該case中的表達式,然後開始檢查每個case中的channel是否可以進行操作,如果有channel可以進行操作,則會執行該case中的表達式。如果有多個case可以執行,則會隨機選擇一個case來執行。如果沒有case可以執行,且沒有default分支,則select會阻塞,直到有case可以執行為止。

select 與 switch 的比較

特性 select switch
主要用途 處理channel的發送和接收操作 基於值判斷
case 只能針對channel判斷 可以是任意的值或表達式
同時滿足多個條件 隨機選擇一個可執行的channel操作 執行第一個匹配的 case,不會同時執行多個分支
是否會阻塞 可能阻塞,直到有channel操作可以進行(若無 default 不會阻塞
default 分支 當沒有channel操作可執行時執行,可用於實現非阻塞的channel操作 當沒有匹配的 case 時執行

使用方式

package main

import (
    "fmt"
    "time"
)

func routine1(c chan string, message int) {
    time.Sleep(2 * time.Second)
    c <- fmt.Sprintf("routine1: %d", message)
    fmt.Println("routine1 done")
}

func routine2(c chan string, message int) {
    time.Sleep(1 * time.Second)
    c <- fmt.Sprintf("routine2: %d", message)
    fmt.Println("routine2 done")
}

func main() {
    c1 := make(chan string)
    c2 := make(chan string)

    after := time.After(10 * time.Second)

    for i := 0; i < 3; i++ {
        go routine1(c1, i)
        go routine2(c2, i)
    }

    for {
        select {
            case msg1, ok := <-c1:
                fmt.Println("Received from", msg1)
                if !ok {
                    break
                }
            case msg2, ok := <-c2:
                fmt.Println("Received from", msg2)
                if !ok {
                    break
                }
            case <-after:
                close(c1)
                close(c2)
                break
            default:
                fmt.Println("Waiting...")
                time.Sleep(500 * time.Millisecond)
        }
    }
}

上面的code中,我們定義了兩個goroutine來模擬傳輸,並且在主goroutine中使用`select來同時監聽兩個channel。當其中一個channel有資料時,就會執行對應的操作。如果目前沒有channel可以接收資料,則會執行default分支,並且在after時間到達後關閉channel。
以上的code的輸出會是:

Waiting...
Received from routine2: 0
Received from routine2: 1
Received from routine1: 0
Waiting...
Received from routine2: 2
Received from routine1: 1
...

但如果你沒有指定default, 就會導致deadlock,這是因為select會一直等待channel有資料可以接收,而沒有任何channel可以接收資料時,select會一直等待且無法跳出。

package main

import (
    "fmt"
    "time"
)

func main(){
    c1 := make(chan string)
    c2 := make(chan string)
    select {
    case msg1 := <-c1:
        fmt.Println("Received from", msg1)
    case msg2 := <-c2:
        fmt.Println("Received from", msg2)
    }
}

Image

我們也可以透過select檢查channel buffer是否已滿, 這樣可以避免在channel buffer已滿時導致的deadlock

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int, 1)
    c <- 1
    select {
    case c <- 2:
        fmt.Println("Sent 2")
    default:
        fmt.Println("Channel is full")
    }
}

注意事項

  • 如果所有的channel都沒有可用的操作,且沒有 default 分支,select 將會阻塞。因此,根據需求決定是否需要添加 default 分支。
  • 當多個channel同時可用時,select 會隨機選擇一個執行。不要依賴特定的執行順序。
  • 如果在接收操作中channel被關閉,接收操作仍然會成功並返回零值。因此,在使用 select 時,要小心處理可能已關閉的channel。
  • 如果在處理case時想提早退出select,只能透過break <variable>來跳出select,不能直接使用break
package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int)
    go func() {
        time.Sleep(2 * time.Second)
        c <- 1
        close(c)
    }()
    LOOP:
    for {
        select {
        case v, ok := <-c:
            if !ok {
                fmt.Println("Channel closed")
                break LOOP
            }
            fmt.Println(v)
        }
    }
}

也可以透過time.After配合select實作Timeout機制。例如:

func timeout(f: func(), timeout time.Duration) {
    done := make(chan bool)
    go func() {
        f()
        done <- true
    }()
    LOOP:
    select {
    case <-done:
        fmt.Println("Done") 
    case <-time.After(timeout):
        fmt.Println("Timeout")
        break LOOP
    }
}

那麼今天的文章就到這告一段落,如果我的文章有任何地方有錯誤請在留言區反應
明天將會介紹Go語言的context包在非同步編程中的應用(如Sync, Timeout, Deadline等),敬請期待
time

REF


上一篇
Go語言中的Channel介紹與使用
下一篇
Day18. context與sync在非同步中的應用
系列文
可以Go一輩子嗎?31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言